package jln

import (
	oceanApi "cid-media-api/api/ocean"
	"cid-media-api/global"
	"cid-media-api/models"
	dingTalkRobot "cid-media-api/util/ding-talk-robot"
	"fmt"
	. "github.com/oceanengine/ad_open_sdk_go/models"
	"github.com/robfig/cron/v3"
	"github.com/samber/lo"
	"gorm.io/gorm/clause"
	"log"
	"os"
	"sync"
	"time"
)

func Run(cronSpec string) {
	c := cron.New()
	_, err := c.AddFunc(cronSpec, func() {
		// 指定日志文件路径
		logFile, err := os.OpenFile("/www/logs/cid-media-api/crontab/get_advertiser_report_day_jln_"+time.Now().Format(time.DateOnly)+".log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
		if err != nil {
			log.Fatal(err)
		}
		defer logFile.Close()

		// 设置日志输出到文件
		logger := log.New(logFile, "", log.LstdFlags)

		startTime := time.Now()
		logger.Printf("====================================================")
		logger.Printf("正在定时执行广告主报表数据拉取任务")
		logger.Printf("任务开始时间： %v", startTime.Format(time.DateTime))

		date := time.Now().Format(time.DateOnly)

		// 如果当前时间是10:00，则执行一次昨天的数据拉取
		if startTime.Hour() == 10 && startTime.Minute() == 00 {
			logger.Printf("当前时间为10:00，正在拉取昨天的回溯数据")
			date = time.Now().AddDate(0, 0, -1).Format(time.DateOnly)
		}
		Execute(0, date, date, logger)

		endTime := time.Now().Format(time.DateTime)
		logger.Printf("任务结束时间： %v", endTime)
		logger.Printf("====================================================")

	})
	if err != nil {
		log.Printf("定时失败：%v", err)
		return
	}

	c.Run()
}

func Execute(advertiserID int, startDate string, endDate string, logger *log.Logger) {
	// 声明存放媒体账户的切片
	var mediaAccounts []models.MediaAccount

	insertCount := 0
	updateCount := 0

	// 查询数据库中的媒体账户，条件是: 状态为0且媒体ID符合要求
	db := global.CidDb.
		Model(&models.MediaAccount{}).
		Where("opt_status = ?", 0).
		Where("media_id = ?", oceanApi.MediaIdOcean).
		Where("company_id = ?", 1)
	// 如果提供了advertiserID，进一步过滤查询结果
	if advertiserID > 0 {
		db = db.Where("advertiser_id = ?", advertiserID)
	}

	// 执行查询，并将结果存入mediaAccounts中，如果出错则打印错误并返回
	err := db.Find(&mediaAccounts).Error
	if err != nil {
		print(err)
		return
	}
	// 声明一个WaitGroup，用于等待所有goroutine完成
	var wg sync.WaitGroup
	// 存放API返回的素材报表数据
	var apiData []*models.ReportDayAdvertiserJln
	apiDataChan := make(chan *models.ReportDayAdvertiserJln) // 创建通道用于传递数据

	// 遍历每个媒体账户
	for _, account := range mediaAccounts {

		// 启动一个新的goroutine来处理API请求
		go func(account *models.MediaAccount) {
			wg.Add(1)               // 增加WaitGroup计数
			defer wg.Done()         // 完成一次数据处理，减少WaitGroup计数
			const pSize int64 = 100 // 每页请求的大小
			var page int64 = 1      // 初始页数
			flag := true            // 循环控制标志
			for {
				if !flag {
					break
				}

				// 调用API获取广告主日报数据
				res := oceanApi.GetAdvertiserReport(*account, startDate, endDate, page, pSize, STAT_TIME_GRANULARITY_DAILY_ReportAdvertiserGetV2TimeGranularity)
				// 如果返回结果为空或列表长度为0,停止循环
				if res == nil || len(res.List) == 0 {
					flag = false // 设置flag为false，终止本次循环
					continue
				}
				page++                              // 增加页数
				if page > *res.PageInfo.TotalPage { // 如果当前页已达到或超过总页数，停止获取更多数据
					flag = false // 设置flag为false，终止本次循环
				}
				// 遍历返回的广告主日报数据
				// 遍历返回的报告数据
				for _, item := range res.List {
					// 增加WaitGroup计数
					formattedRow := models.ReportDayAdvertiserJln{ // 将数据传递到通道
						ReportDayAdvertiser: models.ReportDayAdvertiser{
							MediaId:        account.MediaId,
							CompanyId:      account.CompanyId,
							MediaAccountId: account.MediaAccountId,
							AdvertiserId:   account.AdvertiserId,
							AdvertiserName: account.AdvertiserName,
							Tdate:          *item.StatDatetime,
							Click:          *item.Click,
							PayAmountRoi:   *item.PayAmountRoi,
							ValidPlay:      *item.ValidPlay,
							StatPayAmount:  *item.StatPayAmount,
							Cost:           *item.Cost,
							ConvertCost:    *item.ConvertCost,
							Cpc:            *item.AvgClickCost,
							Show:           *item.Show,
							Ctr:            *item.Ctr,
							Convert:        *item.Convert,
							TotalPlay:      *item.TotalPlay,
							Cpm:            *item.AvgShowCost,
						},
					}
					apiDataChan <- &formattedRow
				}

			}
		}(&account)
	}

	// 启动另一个goroutine用于收集从apiDataChan通道来的数据
	go func() {
		for data := range apiDataChan { // 从通道中接收数据
			apiData = append(apiData, data) // 将接收到的数据添加到apiData切片中
		}
	}()

	wg.Wait()          // 等待所有相关的goroutine完成
	close(apiDataChan) // 确保在所有goroutine完成后关闭通道

	if len(apiData) == 0 { // 如果apiData切片为空，则跳过本次循环
		return
	}

	var existedRecords []models.ReportDayAdvertiserJln
	global.CidDb.Model(models.ReportDayAdvertiserJln{}).
		Where("tdate >= ?", startDate).
		Where("tdate <= ?", endDate).
		Where("media_id = ?", oceanApi.MediaIdOcean).
		Find(&existedRecords)

	var recordsToInsert []models.ReportDayAdvertiserJln
	var recordsToUpdate []models.ReportDayAdvertiserJln

	for _, item := range apiData {
		if lo.SomeBy(existedRecords, func(existed models.ReportDayAdvertiserJln) bool {
			return existed.AdvertiserId == item.AdvertiserId &&
				lo.Substring(existed.Tdate, 0, 10) == lo.Substring(item.Tdate, 0, 10) && // date类型返回的日期格式不一致需要截取前十位进行比较
				existed.MediaId == item.MediaId
		}) {
			// 如果存在相同的记录，则添加进更新列表
			recordsToUpdate = append(recordsToUpdate, *item)
		} else {
			// 如果不存在相同的记录，则添加进插入列表
			recordsToInsert = append(recordsToInsert, *item)
		}
	}

	if len(recordsToUpdate) > 0 {
		// 分批更新数据，防止一次性更新过多数据导致数据库性能下降
		chunks := lo.Chunk(recordsToUpdate, 1000)
		for _, chunk := range chunks {
			err = global.CidDb.Model(models.ReportDayAdvertiserJln{}).Clauses(clause.OnConflict{
				Columns: []clause.Column{
					{Name: "tdate"},
					{Name: "advertiser_id"},
					{Name: "media_id"},
				},
				DoUpdates: clause.AssignmentColumns([]string{
					"advertiser_name",
					"media_account_id",
					"click",
					"pay_amount_roi",
					"valid_play",
					"stat_pay_amount",
					"cost",
					"convert_cost",
					"cpc",
					"show",
					"ctr",
					"convert",
					"total_play",
					"cpm",
				}),
			}).Create(&chunk).Error
			if err != nil {
				_, _ = dingTalkRobot.SendMessage(fmt.Sprintf("go极乐鸟广告主日报，更新失败，错误信息：%v", err))
				logger.Printf("更新失败：%v", err)
			}
		}

		updateCount += len(recordsToUpdate) // 增加更新计数
	}

	// 新增的数据分批插入数据库
	if len(recordsToInsert) > 0 {
		//将recordsToInsert切片分为多个切片，每1000条记录提交一次
		chunks := lo.Chunk(recordsToInsert, 1000)
		for _, chunk := range chunks {
			err = global.CidDb.Model(models.ReportDayAdvertiserJln{}).Create(&chunk).Error
			if err != nil {
				_, _ = dingTalkRobot.SendMessage(fmt.Sprintf("go极乐鸟广告主日报，插入失败，错误信息：%v", err))
				logger.Printf("插入失败：%v", err)
			}
		}
	}

	logger.Printf("插入数量：%v", insertCount)
	logger.Printf("更新数量：%v", updateCount)
}
